package com.cloud.common.loadbalance;

import com.alibaba.cloud.nacos.NacosServiceInstance;
import com.cloud.common.context.AppContext;
import com.cloud.common.context.VersionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.net.URL;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 默认负载均衡器
 *
 * @author fengwenjin
 */
@Slf4j
public class DefaultLoadBalancer {
    private static final String META_PROFILE = "profile";
    private static final String META_VERSION = "version";

    public static ServiceInstance choose(String serviceId) {
        ServiceInstanceListProvider provider = AppContext.getBean(ServiceInstanceListProvider.class);
        List<ServiceInstance> serviceInstanceList = provider.get(serviceId);
        if (CollectionUtils.isEmpty(serviceInstanceList)) {
            return null;
        }

        // 从环境变量读取指定调用地址方便自测
        String requestURL = System.getenv(serviceId);
        if (!StringUtils.isEmpty(requestURL)) {
            return buildServiceInstance(requestURL, serviceId);
        }

        // 过滤调试的服务器并按版本号分组
        String activeEnv = AppContext.getActiveEnv();
        Map<String, List<ServiceInstance>> versionServerMap = serviceInstanceList.stream()
                .filter(instance -> activeEnv.equalsIgnoreCase(instance.getMetadata().get(META_PROFILE)))
                .collect(Collectors.groupingBy(instance -> instance.getMetadata().get(META_VERSION)));

        // 按版本号从高到低排序
        List<String> versionList = versionServerMap.keySet().stream()
                .sorted(Comparator.comparing(String::toString).reversed())
                .collect(Collectors.toList());

        // 根据上下文的版本号选择服务器
        List<ServiceInstance> matchList = null;
        String version = VersionContext.get();
        if (versionServerMap.size() > 1 && version != null) {
            for (String serverVersion : versionList) {
                if (serverVersion.compareTo(version) <= 0) {
                    matchList = versionServerMap.get(serverVersion);
                    break;
                }
            }
        } else {
            // 获取低版本
            version = versionList.get(versionList.size() - 1);
            matchList = versionServerMap.get(version);
        }

        return bestChoose(matchList);
    }

    /**
     * 按最少请求数选择
     *
     * @param serviceInstanceList
     * @return ServiceInstance
     */
    private static ServiceInstance bestChoose(List<ServiceInstance> serviceInstanceList) {
        if (CollectionUtils.isEmpty(serviceInstanceList)) {
            return null;
        }
        // 选择未被熔断并且连接次数最少
        ServiceInstance chooseInstance = null;
        ServiceInstanceStatus chooseInstanceStatus = null;
        for (ServiceInstance instance : serviceInstanceList) {
            ServiceInstanceStatus instanceStatus = ServiceInstanceStatusManager.get(instance);
            // 排除熔断的实例
            if (instanceStatus.isCircuitBreakerTripped()) {
                continue;
            }
            if (chooseInstanceStatus == null) {
                chooseInstance = instance;
                chooseInstanceStatus = instanceStatus;
                continue;
            }
            int activeRequestsCount = instanceStatus.getActiveRequestsCount();
            int totalRequestsCount = instanceStatus.getTotalRequestsCount();
            if (activeRequestsCount <= chooseInstanceStatus.getActiveRequestsCount()
                    && totalRequestsCount <= chooseInstanceStatus.getTotalRequestsCount()) {
                chooseInstance = instance;
                chooseInstanceStatus = instanceStatus;
            }
        }
        return chooseInstance;
    }

    private static ServiceInstance buildServiceInstance(String requestURL, String serviceId) {
        try {
            URL url = new URL(requestURL);
            NacosServiceInstance serviceInstance = new NacosServiceInstance();
            serviceInstance.setServiceId(serviceId);
            serviceInstance.setHost(url.getHost());
            serviceInstance.setPort(url.getPort());
            return serviceInstance;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }
}
